<?php
/**
 * Short description for file
 *
 * Long description for file (if any)...
 *
 * LICENSE: LGPL
 *
 * @category   Sabai
 * @package    Sabai_Container
 * @copyright  Copyright (c) 2006 myWeb Japan (http://www.myweb.ne.jp/)
 * @license    http://opensource.org/licenses/lgpl-license.php GNU LGPL
 * @version    CVS: $Id: Container.php 196 2008-03-01 16:01:41Z mikhail.miguel $
 * @link
 * @since      File available since Release 0.1.1
*/

/**
 * Short description for class
 *
 * Long description for class (if any)...
 *
 * @category   Sabai
 * @package    Sabai_Container
 * @copyright  Copyright (c) 2006 myWeb Japan (http://www.myweb.ne.jp/)
 * @author     Kazumi Ono <onokazu@gmail.com>
 * @license    http://opensource.org/licenses/lgpl-license.php GNU LGPL
 * @version    CVS: $Id: Container.php 196 2008-03-01 16:01:41Z mikhail.miguel $
 * @link
 * @since      Class available since Release 0.1.1
 */
class Sabai_Container
{
    /**
     * @var array
     * @access protected
     */
    var $_components = array();
    /**
     * @var array
     * @access protected
     */
    var $_dependencies = array();

    /**
     * Returns a Sabai_Container instance as singleton
     *
     * @static
     * @staticvar $instance Singleton
     * @return Sabai_Container
     */
    function &getInstance()
    {
        static $instance;
        if (!isset($instance)) {
            $instance = new Sabai_Container();
        }
        return $instance;
    }

    /**
     * Gets a registered component
     *
     * @static
     * @param string $componentName
     * @return object
     */
    function &registry($componentName)
    {
        $container =& Sabai_Container::getInstance();
        $component =& $container->getComponent($componentName);
        return $component;
    }

    /**
     * Registers a component
     *
     * @static
     * @param object $component A class instance or Sabai_Handle
     * @param string $componentName
     */
    function register(&$component, $componentName)
    {
        $container =& Sabai_Container::getInstance();
        $container->setComponent($component, $componentName);
    }

    /**
     * Adds a dependency between components and returns the dependencies defined for the first component
     *
     * @static
     * @return array
     * @param string $componentName
     * @param string $dependencyComponentName
     * @param string $injectionMethod
     * @param bool $injectionRequired
     */
    function dependency($componentName, $dependencyComponentName = '', $injectionMethod = '', $injectionRequired = true)
    {
        $container =& Sabai_Container::getInstance();
        if (!empty($dependencyComponentName) && !empty($injectionMethod)) {
            $container->setComponentDependency($componentName,
                                               $dependencyComponentName,
                                               $injectionMethod,
                                               $injectionRequired);
        }
        return $container->getComponentDependency($componentName);
    }

    /**
     * Checks is a component is already registered
     *
     * @static
     * @param string $componentName
     * @return bool
     */
    function isRegistered($componentName)
    {
        $container =& Sabai_Container::getInstance();
        return $container->hasComponent($componentName);
    }

    /**
     * Gets an instantiated component
     *
     * @param string $componentName
     * @return object
     */
    function &getComponent($componentName)
    {
        // we don't cache the instantiated component here, since that is the work of Sabai_Handle_Cache
        $component =& $this->_components[$componentName]->instantiate();
        foreach ($this->getComponentDependency($componentName) as $dependencyComponentName => $dependency) {
            if (!$this->hasComponent($dependencyComponentName)) {
                $err_level = $dependency['required'] ? E_USER_ERROR : E_USER_WARNING;
                $err_msg = sprintf('Dependency component %s for component %s(class: %s) does not exist, skipping injection',
                                   $dependencyComponentName,
                                   $componentName,
                                   get_class($component));
                trigger_error($error_msg, $err_level);
                continue;
            }
            $method = $dependency['method'];
            if (!method_exists($component, $method)) {
                $err_level = $dependency['required'] ? E_USER_ERROR : E_USER_WARNING;
                $err_msg = sprintf('Dependency injection method %s::%s does not exist, skipping injection',
                                   get_class($component),
                                   $method);
                trigger_error($err_msg, $err_level);
                continue;
            }
            $component->$method($this->getComponent($dependencyComponentName));
        }
        return $component;
    }

    /**
     * Sets a component
     *
     * @param object $component A class instance or Sabai_Handle
     * @param string $componentName
     */
    function setComponent(&$component, $componentName)
    {
        if (!is_a($component, 'Sabai_Handle')) {
            if (!class_exists('Sabai_Handle_Instance')) {
                require 'Sabai/Handle/Instance.php';
            }
            $component =& new Sabai_Handle_Instance($component);
        }
        $this->_components[$componentName] =& $component;
    }

    /**
     * Checks if a component is available
     *
     * @param string $componentName
     * @return bool
     */
    function hasComponent($componentName)
    {
        return isset($this->_components[$componentName]);
    }

    /**
     * Adds a dependency that will be injected through setter injection
     *
     * @param string $componentName
     * @param string $dependencyComponentName
     * @param string $injectionMethod
     * @param bool $injectionRequired
     */
    function setComponentDependency($componentName, $dependencyComponentName, $injectionMethod, $injectionRequired = true)
    {
        if (strcasecmp($componentName, $dependencyComponentName) == 0) {
            trigger_error('Dependency components cannot be the same component', E_USER_WARNING);
        } else {
            $this->_dependencies[$componentName][$dependencyComponentName] = array('method'   => $injectionMethod,
                                                                                   'required' => $injectionRequired);
        }
    }

    /**
     * Gets the current dependency setting for a particular component
     *
     * @return array
     */
    function getComponentDependency($componentName)
    {
        $ret = array();
        if (isset($this->_dependencies[$componentName])) {
            $ret = $this->_dependencies[$componentName];
        }
        return $ret;
    }
}